#pragma once

#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <unordered_map> 

#include "log.hpp"

#define SIZE 1024

class UdpServer
{
public:
    UdpServer(uint16_t port, std::string ip = ""):_port(port),_ip(ip) //初始化ip为"0.0.0.0"
    {}

    ~UdpServer()
    {
        if(_sock >= 0)
            close(_sock);
    }

    bool InitServer()
    {
        // 这里开始进行 网络功能
        // 1. 创建套接字
        _sock = socket(AF_INET, SOCK_DGRAM, 0);
        if(_sock < 0) // 创建失败
        {
            logMessage(FATAL, "%d:%s", errno, strerror(errno)); // 输出错误信息
            exit(2);
        }   

        // 2. bind: 将用户的ip和port在内核中与当前进程 强关联
        // "192.168.xxx.xxx" -> 点分十进制字符串风格 的ip地址
        // 每一个区域的取值范围[0, 255]: 1字节 -> 四个区域
        // 点分十进制字符串风格 的ip地址 <-> 4字节
        struct sockaddr_in local;
        bzero(&local, sizeof(local)); // bzero(s,n), 将 s 指向的内存区域的前 n 个字节全部设置为 0：同理memset
        local.sin_family = AF_INET; // AF_INET 地址族，表示 IPv4 地址
        // 通过网络通信时，服务器的ip和port一样需要发送给对方主机: 先将数据发送到网络
        local.sin_port = htons(_port); // 将本地端口号转为网络字节序
        // I. 先将点分十进制字符串风格的ip 转为 4字节
        // II. 再 4字节主机序列 -> 网络序列
        local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str()); // 该接口直接完成上述两部操作
        if(bind(_sock, (struct sockaddr*)&local, sizeof(local)) < 0)
        {
            logMessage(FATAL, "%d:%s", errno, strerror(errno));
            exit(2);
        }
        // 初始化成功 输出信息
        logMessage(NORMAL, "init udp server done >>> %s", strerror(errno));

        return true;
    }   

    void Start()
    {
        // 网络服务器，永不退出
        // 服务器启动-> 进程 -> 常驻进程 -> 永远在内存中存在（除非挂掉
        // echo server: client给服务器发送消息，服务器将信息返回
        char buffer[SIZE];
        for( ; ; )
        {
            // peer - 纯输出型参数
            struct sockaddr_in peer;
            bzero(&peer, sizeof(peer));
            // 输入: peer 缓冲区大小
            // 输出: 实际读到的peer
            socklen_t len = sizeof(peer);
            //start. 读取数据
            ssize_t s = recvfrom(_sock, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);
            if(s > 0)
            {
                buffer[s] = 0; // 将目前数据当做字符串
                uint16_t cli_port = ntohs(peer.sin_port); // 网络字节序 -> 主机字节序
                std::string cli_ip = inet_ntoa(peer.sin_addr); //4字节的网络序列的IP->本主机的字符串风格的IP，方便显示
                printf("[%s:%d]# %s\n", cli_ip.c_str(), cli_port, buffer);
            }
            //分析和处理数据，TODO
            // 写回数据
            sendto(_sock, buffer, strlen(buffer), 0, (struct sockaddr*)&peer, len);
        }
    }

private:
    // 一个服务器一定需要 端口号和ip
    uint16_t _port;  // 端口号
    std::string _ip; // ip
    int _sock;
};